using System.Threading;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using SageKing.Core.Options;
using SageKing.Database.EF.Entity;
using SageKing.OpenIddict.UI.Identity.Infrastructure;
using SageKing.OpenIddict.UI.Infrastructure;
using SageKing.OpenIddict.UI.Infrastructure.Data;
using static OpenIddict.Abstractions.OpenIddictConstants;

namespace SageKing.OpenIddict.OpenIdService;

public interface IMigrationService
{
    Task EnsureMigrationAsync(OpenIdServerOptions openIdOptions);
}

public class MigrationService : IMigrationService
{
    private readonly IServiceProvider _serviceProvider;
    public OpenIdServerOptions _options { get; set; }

    public MigrationService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task EnsureMigrationAsync(OpenIdServerOptions openIdOptions)
    {
        _options = openIdOptions;

        using var scope = _serviceProvider.CreateScope();

        var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        //await context.Database.EnsureCreatedAsync();
        await context.Database.MigrateAsync();

        var uiContext = scope.ServiceProvider.GetRequiredService<OpenIddictUIContext>();
        //await uiContext.Database.EnsureCreatedAsync();
        await uiContext.Database.MigrateAsync();

        var uiIdentityContext = scope.ServiceProvider.GetRequiredService<OpenIddictUIIdentityContext>();
        //await uiIdentityContext.Database.EnsureCreatedAsync();
        await uiIdentityContext.Database.MigrateAsync();
         
        var tmpTemplateContext = scope.ServiceProvider.GetRequiredService<TemplateContext>();
        //await tmpTemplateContext.Database.EnsureCreatedAsync();
        await tmpTemplateContext.Database.MigrateAsync();

        await RegisterApplicationsAsync(scope.ServiceProvider);
        await RegisterScopesAsync(scope.ServiceProvider);

        await EnsureAdministratorRole(scope.ServiceProvider);
        await EnsureAdministratorUser(scope.ServiceProvider);
    }

    private async Task RegisterApplicationsAsync(IServiceProvider provider)
    {
        var manager = provider.GetRequiredService<IOpenIddictApplicationManager>();

        if (await manager.FindByClientIdAsync(_options.ClientId) is null)
        {
            await manager.CreateAsync(new OpenIddictApplicationDescriptor
            {
               
                ClientId = _options.ClientId,
                ClientSecret = _options.ClientSecret,
                ConsentType = ConsentTypes.Implicit,
                DisplayName = "SPA Client Application",
                PostLogoutRedirectUris =
          {
            new Uri(_options.Issuer)
          },
                RedirectUris =
          {
            new Uri(_options.Issuer)
          },
                Permissions =
          {
            Permissions.Endpoints.Authorization,
            Permissions.Endpoints.EndSession,
            Permissions.Endpoints.Token,
            Permissions.GrantTypes.ClientCredentials,
            Permissions.GrantTypes.AuthorizationCode,
            Permissions.GrantTypes.RefreshToken,
            Permissions.ResponseTypes.Code,
            Permissions.Scopes.Email,
            Permissions.Scopes.Profile,
            Permissions.Scopes.Roles,
            Permissions.Prefixes.Scope + $"server{ScopeConst.PrefixEnd}"
          },
                Requirements =
          {
            Requirements.Features.ProofKeyForCodeExchange
          }
            });
        }


        if (await manager.FindByClientIdAsync(_options.ResourceServer.ClientId) is null)
        {
            await manager.CreateAsync(new OpenIddictApplicationDescriptor
            {
                ClientId = _options.ResourceServer.ClientId,

                DisplayName = "Server API Service",
                ClientSecret = _options.ResourceServer.ClientSecret,
                Permissions =
                {
                    Permissions.Endpoints.Introspection
                }
            });
        }
    }

    private async Task RegisterScopesAsync(IServiceProvider provider)
    {
        var manager = provider.GetRequiredService<IOpenIddictScopeManager>();

        var tmpserver_scope = $"server{ScopeConst.PrefixEnd}";
        if (await manager.FindByNameAsync(tmpserver_scope) is null)
        {
            await manager.CreateAsync(new OpenIddictScopeDescriptor
            {
                Name = tmpserver_scope,
                DisplayName = "Server scope access",
                Resources =
                {
                  _options.ResourceServer.ClientId
                }
            });
        }
    }

    private async Task EnsureAdministratorRole(IServiceProvider provider)
    {
        var manager = provider.GetRequiredService<RoleManager<AppRole>>();

        var role = RolesConst.Administrator;
        var roleExists = await manager.RoleExistsAsync(role);
        if (!roleExists)
        {
            var newRole = new AppRole(role);
            await manager.CreateAsync(newRole);
        }
    }

    private async Task EnsureAdministratorUser(IServiceProvider provider)
    {
        var manager = provider.GetRequiredService<UserManager<AppUser>>();
        var tmpOpenIdOptions = provider.GetRequiredService<IOptions<OpenIdServerOptions>>().Value;

        var user = await manager.FindByNameAsync(tmpOpenIdOptions.AdminMailAddress);
        if (user != null)
        {
            return;
        }

        var AppUser = new AppUser
        {
            UserName = tmpOpenIdOptions.AdminMailAddress,
            Email = tmpOpenIdOptions.AdminMailAddress
        };

        var userResult = await manager.CreateAsync(AppUser, tmpOpenIdOptions.AdminPwd);
        if (!userResult.Succeeded)
        {
            return;
        }

        await manager.SetLockoutEnabledAsync(AppUser, false);
        await manager.AddToRoleAsync(AppUser, RolesConst.Administrator);
    }
}
